﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;

namespace IMModel.Domain.Common
{


    /// <summary>
    /// Коллекция индексов респондентов
    /// </summary>
    public class RespondentIndexCollection : ICacheble, IEnumerable<KeyValuePair<Respondent, double>>
    {
        private readonly IDictionary<Respondent, double> _cache = new Dictionary<Respondent, double>(new RespondentComparier());
        private double? _maxValue = null;

        private static RespondentIndexCollection DoVectorAction(RespondentIndexCollection source, RespondentIndexCollection other, Func<double, double, double> func)
        {
            Contract.Requires(func != null);

            // Сложение вида A + 0 или 0 + A
            var collection = other ?? source;

            // Сложение 0 + 0
            if (collection == null) return null;

            RespondentIndexCollection target = new RespondentIndexCollection();

            foreach (var item in collection._cache)
                target.Add(item.Key, item.Value);


            if ((source != null) && (other != null))
            {
                foreach (var otherItem in source._cache)
                {
                    if (target.ContainsKey(otherItem.Key))
                        target[otherItem.Key] = func(target[otherItem.Key], otherItem.Value);
                    else target.Add(otherItem.Key, otherItem.Value);
                }
            }
            return target;
        }

        private bool ContainsKey(Respondent respondent)
        {
            if (respondent == null) throw new ArgumentNullException("respondent");
            return _cache.ContainsKey(respondent);
        }

        /// <summary>
        /// Производит сложение двух коллекций.
        /// </summary>
        /// <param name="source"></param>
        /// <param name="other"></param>
        /// <returns></returns>
        public static RespondentIndexCollection operator +(RespondentIndexCollection source, RespondentIndexCollection other)
        {
            return DoVectorAction(source, other, (x, y) => x + y);
        }

        public static RespondentIndexCollection operator /(RespondentIndexCollection source, RespondentIndexCollection other)
        {
            return DoVectorAction(source, other, (x, y) => x / y);
        }

        /// <summary>
        /// Производит умножение коллекции (вектора) на число
        /// </summary>
        /// <param name="source"></param>
        /// <param name="val"></param>
        /// <returns></returns>
        public static RespondentIndexCollection operator *(RespondentIndexCollection source, double val)
        {
            return DoScalarAction(source, val, (x, y) => x * y);
        }
        /// <summary>
        /// Производит деление коллекции (вектора) на число
        /// </summary>
        /// <param name="source"></param>
        /// <param name="val"></param>
        /// <returns></returns>
        public static RespondentIndexCollection operator /(RespondentIndexCollection source, double val)
        {
            return DoScalarAction(source, val, (x, y) => x / y);
        }
        /// <summary>
        /// Очищает коллекцию индексов
        /// </summary>
        internal void Clear()
        {
            _cache.Clear();
            Invalidate();
        }

        /// <summary>
        /// Получает максимальное значение индекса в коллекции
        /// </summary>
        public double MaxValue
        {
            get
            {
                if (_maxValue == null)
                    _maxValue = _cache.Count > 0 ? _maxValue = _cache.Max(x => x.Value) : 0;
                return _maxValue.Value;
            }
        }

        /// <summary>
        /// Получает индекс заданного респондента в коллекции
        /// </summary>
        /// <param name="respondent"></param>
        /// <returns></returns>
        public double this[Respondent respondent]
        {
            get
            {
                Contract.Requires(respondent != null);

                double result;
                _cache.TryGetValue(respondent, out result);
                return result;
            }
            set
            {
                Contract.Requires(respondent != null);
                Contract.Ensures(_cache.ContainsKey(respondent));
                _cache[respondent] = value;
            }
        }

        /// <summary>
        /// Добавляет значение индекса респондента в коллекцию. Если такой респондент уже существует в коллекции, то производится сложение его существующего индекса с переданным параметром
        /// </summary>
        /// <param name="key">Респондент</param>
        /// <param name="value">Значение индекса (объёма)</param>
        public void Add(Respondent key, double value)
        {
            if (_cache.ContainsKey(key))
                _cache[key] += value;
            else
                _cache.Add(key, value);

            Invalidate();
        }

        private static RespondentIndexCollection DoScalarAction(RespondentIndexCollection source, double val, Func<double, double, double> func)
        {
            if (source == null) return null;
            if (func == null) throw new ArgumentNullException("func");
            var newCollection = new RespondentIndexCollection();

            foreach (var item in source._cache)
            {
                newCollection.Add(item.Key, func(item.Value, val));
            }

            return newCollection;
        }

        public void Invalidate()
        {
            _maxValue = null;
        }

        public int Count { get { return _cache.Count; } }

        public override string ToString()
        {
            return string.Join("; " /*"\n\r"*/, _cache.Select(d =>
                string.Format("{0}-{1}", d.Key.Person, d.Value)));
            //String result="";
            //foreach (var d in _cache)
            //{
            //    result += string.Format("R:{0} I:{1}", d.Key.Person, d.Value);
            //}
            //return result;
        }

        public IEnumerator GetEnumerator()
        {
            return _cache.GetEnumerator();
        }

        IEnumerator<KeyValuePair<Respondent, double>> IEnumerable<KeyValuePair<Respondent, double>>.GetEnumerator()
        {
            return _cache.GetEnumerator();
        }

        public bool ContainsRespondent(Respondent respondent)
        {
            Contract.Requires(respondent != null);
            return _cache.ContainsKey(respondent);
        }
    }
}